昨天證明 0x40000000 能讀到配置的實體頁,今天把位址改成 0x40000034。
它應該仍然查到同一個 frame,只是最後的 PA 多出 0x34,也就是十進位 52 bytes。
我們要寫一個軟體查詢函式,把兩層 PTE 與最後的位址印出來。
之後分頁出錯時,就不用只看一個 Page Fault 原因碼猜測問題。
《Operating System Concepts》第 10 版第 9.4.1 節,用階層式分頁說明大位址空間的頁表管理問題。
32-bit 位址配合 4 KiB 頁面有約一百萬個虛擬頁,若一次為全部位址建立四-byte 表項,單張表就需要約 4 MiB。
分成多層後,不需要使用的大片範圍可以先不配置下一層頁表。
今天的 vm_map() 就是遇到未建立的根表項時,才動態配置第二層。
這把 Day 09 的記憶體配置與 Day 14 的映射連在一起,也說明為何頁表本身同樣消耗 RAM。
在 30-days-os-kernel/examples/tiny-kernel/ 執行:
make DAY=15 run
完整程式在 vm.c,以下是 vm_walk():
pte_t *vm_walk(pte_t *root, uint32_t va) {
pte_t e = root[va >> 22];
if (!(e & PTE_V) || (e & (PTE_R | PTE_W | PTE_X))) return NULL;
pte_t *table = (pte_t *) ((e >> 10) << 12);
pte_t *leaf = &table[(va >> 12) & 1023];
return (*leaf & PTE_V) && (*leaf & (PTE_R | PTE_X)) ? leaf : NULL;
}
根表項先檢查 V,再確認它是指向下一層的表項。
此 helper 不支援根層直接作為 4 MiB 葉節點,所以看到 R/W/X 會拒絕,避免把資料頁誤當成頁表讀取。
取得第二層後,以 VPN[0] 選出 leaf,再檢查是否存在有效映射。
目前頁表都是核心依 vm_map() 建立,這個 helper 不負責驗證任意來源的不可信頁表,也沒有完整模擬硬體所有權限規則。
頁內 offset 不存在於 PTE 的 PPN 欄位,要由原始 VA 取回:
return ((*leaf >> 10) << 12) | (va & 4095);
對 0x40000034 而言,VPN[1] 是 256,VPN[0] 是 0,offset 是 52。
如果實體頁起點為 P,結果就是 P + 52。
這個加法說明了一個常見錯誤:把頁表解出的 PA 起點直接回傳,卻忘記加上 offset,會讓所有頁內存取都落到頁首。
檢查只碰到對齊地址時可能看不出來,所以今天刻意使用非零 offset。
vm_dump() 會印出 VA 的拆解、兩層原始 PTE 與最後 PA。
正常結果應有:
VA=0x40000000 vpn1=256 vpn0=0 offset=0
L1=0x... L0=0x... PA=0x...
VA=0x40000034 vpn1=256 vpn0=0 offset=52
L1=0x... L0=0x... PA=0x...
offset and unmapped checks=ok
兩筆的 L1 與 L0 值應相同,PA 則相差 0x34。
不要只看 dump 有沒有出現,還要確認這三個關係。
實驗還檢查 vm_walk(root, 0x50000000) == NULL。
這是讓軟體 walker 回報未映射,沒有真的對該地址執行 load,也沒有觸發硬體 Page Fault。
兩種測試回答不同問題,不能把 NULL 結果描述成 trap handler 已經處理分頁錯誤。
vm_map() 檢查 VA 與 PA 都以頁面對齊,也拒絕覆蓋既有映射。
對 leaf 權限還會確認可寫時也可讀,避免建立 RISC-V 不允許的 R=0、W=1 組合。
可以在副本中對相同 VA 呼叫兩次 vm_map(),第二次應停止於重複映射檢查。
完成後恢復原本版本,正式的 remap 操作需要明確處理舊頁面、PTE 更新與轉譯同步,不能把「覆寫成功」當成完整實作。
《Operating System Concepts》第 9.3.2 節介紹轉譯後備緩衝區(Translation Lookaside Buffer,TLB),用來保留常用的位址轉譯結果。
因此修改 RAM 裡的 PTE,不代表 CPU 一定立刻停止使用舊的轉譯。
我們在切換頁表時執行 sfence.vma,先用完整同步處理單核心實驗。
今天沒有量測 TLB 命中率,也不把 vm_walk() 的執行時間當成硬體 MMU 的成本。
到多核心環境,其他 hart 的舊轉譯也需要協調處理。
本篇主要檔案為 vm.c 的 vm_walk()、vm_translate() 與 vm_dump(),建議 commit 訊息:
day15: add page-table walk and dump
Day 16 會把一份真正的使用者程式映像放進配置頁,替程式碼與堆疊建立不同權限。